package org.jetbrains.exposed.v1.jdbc.statements.jdbc

import org.jetbrains.exposed.v1.core.ArrayColumnType
import org.jetbrains.exposed.v1.core.BinaryColumnType
import org.jetbrains.exposed.v1.core.BlobColumnType
import org.jetbrains.exposed.v1.core.IColumnType
import org.jetbrains.exposed.v1.core.InternalApi
import org.jetbrains.exposed.v1.core.VarCharColumnType
import org.jetbrains.exposed.v1.core.statements.StatementResult
import org.jetbrains.exposed.v1.core.vendors.SQLiteDialect
import org.jetbrains.exposed.v1.core.vendors.currentDialect
import org.jetbrains.exposed.v1.jdbc.statements.api.JdbcPreparedStatementApi
import java.io.InputStream
import java.sql.PreparedStatement
import java.sql.Statement
import java.sql.Types

/**
 * Class representing a precompiled SQL [statement] from the JDBC SPI.
 *
 * The result set generated by executing this statement contains auto-generated keys based on the value of
 * [wasGeneratedKeysRequested].
 */
class JdbcPreparedStatementImpl(
    val statement: PreparedStatement,
    val wasGeneratedKeysRequested: Boolean
) : JdbcPreparedStatementApi {
    override val resultSet: JdbcResult?
        get() = when {
            !wasGeneratedKeysRequested -> statement.resultSet?.let { JdbcResult(it) }
            currentDialect is SQLiteDialect -> {
                statement.connection.prepareStatement("select last_insert_rowid();").executeQuery()?.let { JdbcResult(it) }
            }
            else -> statement.generatedKeys?.let { JdbcResult(it) }
        }

    override var fetchSize: Int?
        get() = statement.fetchSize
        set(value) {
            value?.let { statement.fetchSize = value }
        }

    override var timeout: Int?
        get() = statement.queryTimeout
        set(value) {
            value?.let { statement.queryTimeout = it }
        }

    override fun addBatch() {
        statement.addBatch()
    }

    override fun executeQuery(): JdbcResult = JdbcResult(statement.executeQuery())

    override fun executeUpdate(): Int = statement.executeUpdate()

    override fun executeMultiple(): List<StatementResult> {
        // execute() returns true only if first result is a ResultSet
        return if (statement.execute()) {
            listOf(StatementResult.Object(JdbcResult(statement.resultSet)))
        } else {
            // getMoreResults() returns true only if next result is a ResultSet
            while (!statement.getMoreResults(Statement.CLOSE_CURRENT_RESULT)) {
                if (statement.updateCount == -1) return emptyList()
            }
            listOf(StatementResult.Object(JdbcResult(statement.resultSet)))
        }
    }

    @Deprecated(
        message = "This operator function will be removed in future releases. " +
            "Replace with the `set(index, value, this)` operator that accepts a third argument for the IColumnType of the parameter value being bound.",
        level = DeprecationLevel.ERROR
    )
    override fun set(index: Int, value: Any) {
        set(index, value, VarCharColumnType())
    }

    override fun set(index: Int, value: Any, columnType: IColumnType<*>) {
        statement.setObject(index, value)
    }

    override fun setNull(index: Int, columnType: IColumnType<*>) {
        if (columnType is BinaryColumnType || (columnType is BlobColumnType && !columnType.useObjectIdentifier)) {
            statement.setNull(index, Types.LONGVARBINARY)
        } else {
            statement.setObject(index, null)
        }
    }

    override fun setInputStream(index: Int, inputStream: InputStream, setAsBlobObject: Boolean) {
        if (setAsBlobObject) {
            statement.setBlob(index, inputStream)
        } else {
            statement.setBinaryStream(index, inputStream, inputStream.available())
        }
    }

    @Deprecated(
        message = "This function will be removed in future releases. " +
            "Replace with the method `setArray(index, this, array)` that accepts an ArrayColumnType as the second argument instead of a string type representation.",
        level = DeprecationLevel.ERROR
    )
    override fun setArray(index: Int, type: String, array: Array<*>) {
        @OptIn(InternalApi::class)
        setArray(index, getArrayColumnType(type), array)
    }

    override fun setArray(index: Int, type: ArrayColumnType<*, *>, array: Array<*>) {
        statement.setArray(index, statement.connection.createArrayOf(type.delegateType, array))
    }

    override fun closeIfPossible() {
        if (!statement.isClosed) statement.close()
    }

    override fun executeBatch(): List<Int> {
        return statement.executeBatch().map {
            when (it) {
                Statement.SUCCESS_NO_INFO -> 1
                Statement.EXECUTE_FAILED -> 0
                else -> it
            }
        }
    }

    override fun cancel() {
        if (!statement.isClosed) statement.cancel()
    }
}
